/*
    Matali Physics Demo
    Copyright (c) 2011 KOMIRES Sp. z o. o.
 */
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Komires.MataliPhysics;

namespace MataliPhysicsDemo
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Camera3Draw1
    {
        Demo demo;
        PhysicsScene scene;
        string instanceIndexName;

        string defaultName;

        public bool EnableDrawBoundingBoxes;
        public bool EnableDrawContactPoints;
        public bool EnableDrawSlipingObjects;

        BasicEffect effect;
        VertexPositionColor[] cameraVertices;

        Vector3 position;
        Vector3 direction;
        Matrix transform;
        Matrix projection;
        Matrix view;
        Matrix world;
        BoundingBox boundingBox;

        Vector3 ambient;
        Vector3 diffuse;
        Vector3 emission;
        Vector3 specular;

        Vector3 lightDirection;
        Vector3 lightDiffuse;
        Vector3 lightSpecular;

        Matrix matrixIdentity;

        public Camera3Draw1(Demo demo, int instanceIndex)
        {
            this.demo = demo;
            instanceIndexName = " " + instanceIndex.ToString();

            defaultName = "Default";

            cameraVertices = new VertexPositionColor[24];

            matrixIdentity = Matrix.Identity;
        }

        public void Initialize(PhysicsScene scene)
        {
            this.scene = scene;

            EnableDrawBoundingBoxes = false;
            EnableDrawContactPoints = false;
            EnableDrawSlipingObjects = false;

            effect = new BasicEffect(demo.GraphicsDevice);
        }

        public void SetControllers(bool enableDrawBoundingBoxes, bool enableDrawContactPoints, bool enableDrawSlipingObjects)
        {
            this.EnableDrawBoundingBoxes = enableDrawBoundingBoxes;
            this.EnableDrawContactPoints = enableDrawContactPoints;
            this.EnableDrawSlipingObjects = enableDrawSlipingObjects;

            PhysicsObject objectBase = scene.Factory.PhysicsObjectManager.Find("Camera 3" + instanceIndexName);

            if (objectBase != null)
            {
                objectBase.Camera.UserTagObj = this;
                objectBase.UserControllers.DrawMethods += new DrawMethod(Draw);
                objectBase.UserControllers.DrawMethods += new DrawMethod(DrawBoundingBoxes);
                objectBase.UserControllers.DrawMethods += new DrawMethod(DrawContactPoints);
            }
        }

        void Draw(DrawMethodArgs args)
        {
            PhysicsScene scene = demo.Engine.Factory.PhysicsSceneManager.Get(args.OwnerSceneIndex);
            PhysicsObject objectBase = scene.Factory.PhysicsObjectManager.Get(args.OwnerIndex);

            if (!objectBase.Camera.Enabled) return;

            float time = args.Time;

            PhysicsObject drawPhysicsObject;
            PhysicsObject transparentPhysicsObject;
            DemoMesh mesh;
            Matrix worldBone;

            objectBase.Camera.View.GetViewMatrix(ref view);
            objectBase.Camera.Projection.GetProjectionMatrix(ref projection);

            ModelBone parentBone = null;
            PhysicsLight sceneLight = scene.Light;
            sceneLight.GetDirection(ref lightDirection);
            sceneLight.GetDiffuse(ref lightDiffuse);
            sceneLight.GetSpecular(ref lightSpecular);

            for (int i = 0; i < objectBase.Camera.DrawPhysicsObjectCount; i++)
            {
                drawPhysicsObject = objectBase.Camera.GetDrawPhysicsObject(i);

                if ((drawPhysicsObject.UserControllers.DrawMethods == null) || (drawPhysicsObject == objectBase))
                {
                    if (drawPhysicsObject.UserTagStr == null)
                        continue;

                    if ((drawPhysicsObject.RigidGroupOwner != drawPhysicsObject) && (drawPhysicsObject.UserTagStr != null) && (drawPhysicsObject.RigidGroupOwner.UserTagStr != null))
                        continue;

                    drawPhysicsObject.MainWorldTransform.GetTransformMatrix(ref world);

                    mesh = demo.Meshes[drawPhysicsObject.UserTagStr];

                    if (mesh.Model != null)
                    {
                        drawPhysicsObject.Material.GetAmbient(ref ambient);
                        drawPhysicsObject.Material.GetDiffuse(ref diffuse);
                        drawPhysicsObject.Material.GetEmission(ref emission);
                        drawPhysicsObject.Material.GetSpecular(ref specular);

                        if (drawPhysicsObject.RigidGroupOwner.IsSleeping && EnableDrawSlipingObjects)
                        {
                            diffuse.X = 0.7f;
                            diffuse.Y = 1.0f;
                            diffuse.Z = 1.0f;
                        }

                        demo.GraphicsDevice.RasterizerState = RasterizerState.CullClockwise;

                        Model model = mesh.Model;

                        foreach (ModelMesh modelMesh in model.Meshes)
                        {
                            foreach (BasicEffect effect in modelMesh.Effects)
                            {
                                parentBone = model.Bones[modelMesh.ParentBone.Index].Parent;

                                if (parentBone != null)
                                {
                                    transform = parentBone.Transform;
                                    Matrix.Multiply(ref transform, ref world, out worldBone);
                                }
                                else
                                {
                                    worldBone = world;
                                }

                                effect.View = view;
                                effect.World = worldBone;
                                effect.Projection = projection;

                                if (drawPhysicsObject.Material.UserTagStr != null)
                                {
                                    effect.TextureEnabled = true;
                                    effect.Texture = demo.Textures[drawPhysicsObject.Material.UserTagStr];
                                }
                                else
                                {
                                    effect.TextureEnabled = true;
                                    effect.Texture = demo.Textures[defaultName];
                                }

                                effect.AmbientLightColor = ambient;
                                effect.DiffuseColor = diffuse;
                                effect.SpecularColor = specular;
                                effect.EmissiveColor = emission;
                                effect.SpecularPower = drawPhysicsObject.Material.SpecularPower;
                                effect.Alpha = drawPhysicsObject.Material.TransparencyFactor;

                                effect.DirectionalLight0.Direction = lightDirection;
                                effect.DirectionalLight0.DiffuseColor = lightDiffuse;
                                effect.DirectionalLight0.SpecularColor = lightSpecular;
                                effect.DirectionalLight0.Enabled = true;
                                effect.LightingEnabled = true;
                                effect.PreferPerPixelLighting = true;
                            }

                            modelMesh.Draw();
                        }
                    }
                    else
                    {
                        mesh.Effect.View = view;
                        mesh.Effect.World = world;
                        mesh.Effect.Projection = projection;

                        mesh.Effect.DirectionalLight0.Direction = lightDirection;
                        mesh.Effect.DirectionalLight0.DiffuseColor = lightDiffuse;
                        mesh.Effect.DirectionalLight0.SpecularColor = lightSpecular;
                        mesh.Effect.DirectionalLight0.Enabled = true;
                        mesh.Effect.LightingEnabled = true;
                        mesh.Effect.PreferPerPixelLighting = true;

                        mesh.Draw(drawPhysicsObject.Material, drawPhysicsObject.RigidGroupOwner.IsSleeping && EnableDrawSlipingObjects);
                    }
                }
                else
                {
                    if (drawPhysicsObject.UserControllers.EnableDraw)
                        continue;

                    if ((drawPhysicsObject.RigidGroupOwner != drawPhysicsObject) && (drawPhysicsObject.UserTagStr != null) && (drawPhysicsObject.RigidGroupOwner.UserTagStr != null))
                        continue;

                    drawPhysicsObject.UserControllers.DrawMethodArgs.Time = time;
                    drawPhysicsObject.UserControllers.DrawMethodArgs.OwnerIndex = drawPhysicsObject.Index;
                    drawPhysicsObject.UserControllers.DrawMethodArgs.OwnerSceneIndex = scene.Index;
                    drawPhysicsObject.UserControllers.DrawMethods(drawPhysicsObject.UserControllers.DrawMethodArgs);
                }
            }

            if (objectBase.Camera.TransparentPhysicsObjectCount != 0)
            {
                demo.GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
                demo.GraphicsDevice.BlendState = BlendState.AlphaBlend;

                for (int i = 0; i < objectBase.Camera.TransparentPhysicsObjectCount; i++)
                {
                    transparentPhysicsObject = objectBase.Camera.GetTransparentPhysicsObject(i);

                    if ((transparentPhysicsObject.UserControllers.DrawMethods == null) || (transparentPhysicsObject == objectBase))
                    {
                        if (transparentPhysicsObject.UserTagStr == null)
                            continue;

                        if ((transparentPhysicsObject.RigidGroupOwner != transparentPhysicsObject) && (transparentPhysicsObject.UserTagStr != null) && (transparentPhysicsObject.RigidGroupOwner.UserTagStr != null))
                            continue;

                        transparentPhysicsObject.MainWorldTransform.GetTransformMatrix(ref world);

                        mesh = demo.Meshes[transparentPhysicsObject.UserTagStr];

                        if (mesh.Model != null)
                        {
                            transparentPhysicsObject.Material.GetAmbient(ref ambient);
                            transparentPhysicsObject.Material.GetDiffuse(ref diffuse);
                            transparentPhysicsObject.Material.GetEmission(ref emission);
                            transparentPhysicsObject.Material.GetSpecular(ref specular);

                            if (transparentPhysicsObject.RigidGroupOwner.IsSleeping && EnableDrawSlipingObjects)
                            {
                                diffuse.X = 0.7f;
                                diffuse.Y = 1.0f;
                                diffuse.Z = 1.0f;
                            }

                            demo.GraphicsDevice.RasterizerState = RasterizerState.CullClockwise;

                            Model model = mesh.Model;

                            foreach (ModelMesh modelMesh in model.Meshes)
                            {
                                foreach (BasicEffect effect in modelMesh.Effects)
                                {
                                    parentBone = model.Bones[modelMesh.ParentBone.Index].Parent;

                                    if (parentBone != null)
                                    {
                                        transform = parentBone.Transform;
                                        Matrix.Multiply(ref transform, ref world, out worldBone);
                                    }
                                    else
                                    {
                                        worldBone = world;
                                    }

                                    effect.View = view;
                                    effect.World = worldBone;
                                    effect.Projection = projection;

                                    if (transparentPhysicsObject.Material.UserTagStr != null)
                                    {
                                        effect.TextureEnabled = true;
                                        effect.Texture = demo.Textures[transparentPhysicsObject.Material.UserTagStr];
                                    }
                                    else
                                    {
                                        effect.TextureEnabled = true;
                                        effect.Texture = demo.Textures[defaultName];
                                    }

                                    effect.AmbientLightColor = ambient;
                                    effect.DiffuseColor = diffuse;
                                    effect.SpecularColor = specular;
                                    effect.EmissiveColor = emission;
                                    effect.SpecularPower = transparentPhysicsObject.Material.SpecularPower;
                                    effect.Alpha = transparentPhysicsObject.Material.TransparencyFactor;

                                    effect.DirectionalLight0.Direction = lightDirection;
                                    effect.DirectionalLight0.DiffuseColor = lightDiffuse;
                                    effect.DirectionalLight0.SpecularColor = lightSpecular;
                                    effect.DirectionalLight0.Enabled = true;
                                    effect.LightingEnabled = true;
                                    effect.PreferPerPixelLighting = true;
                                }

                                modelMesh.Draw();
                            }
                        }
                        else
                        {
                            mesh.Effect.View = view;
                            mesh.Effect.World = world;
                            mesh.Effect.Projection = projection;

                            mesh.Effect.DirectionalLight0.Direction = lightDirection;
                            mesh.Effect.DirectionalLight0.DiffuseColor = lightDiffuse;
                            mesh.Effect.DirectionalLight0.SpecularColor = lightSpecular;
                            mesh.Effect.DirectionalLight0.Enabled = true;
                            mesh.Effect.LightingEnabled = true;
                            mesh.Effect.PreferPerPixelLighting = true;

                            mesh.Draw(transparentPhysicsObject.Material, transparentPhysicsObject.RigidGroupOwner.IsSleeping && EnableDrawSlipingObjects);
                        }
                    }
                    else
                    {
                        if (transparentPhysicsObject.UserControllers.EnableDraw)
                            continue;

                        if ((transparentPhysicsObject.RigidGroupOwner != transparentPhysicsObject) && (transparentPhysicsObject.UserTagStr != null) && (transparentPhysicsObject.RigidGroupOwner.UserTagStr != null))
                            continue;

                        transparentPhysicsObject.UserControllers.DrawMethodArgs.Time = time;
                        transparentPhysicsObject.UserControllers.DrawMethodArgs.OwnerIndex = transparentPhysicsObject.Index;
                        transparentPhysicsObject.UserControllers.DrawMethodArgs.OwnerSceneIndex = scene.Index;
                        transparentPhysicsObject.UserControllers.DrawMethods(transparentPhysicsObject.UserControllers.DrawMethodArgs);
                    }
                }

                demo.GraphicsDevice.BlendState = BlendState.Opaque;
                demo.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            }
        }

        public void DrawBoundingBoxes(DrawMethodArgs args)
        {
            PhysicsScene scene = demo.Engine.Factory.PhysicsSceneManager.Get(args.OwnerSceneIndex);
            PhysicsObject objectBase = scene.Factory.PhysicsObjectManager.Get(args.OwnerIndex);

            if (!objectBase.Camera.Enabled) return;
            if (!objectBase.Camera.Active) return;
            if (!EnableDrawBoundingBoxes) return;

            float time = args.Time;

            PhysicsObject visiblePhysicsObject;
            Vector3 min, max;

            objectBase.Camera.View.GetViewMatrix(ref view);
            objectBase.Camera.Projection.GetProjectionMatrix(ref projection);
            world = matrixIdentity;
            Color diffuseColor = Color.White;

            for (int i = 0; i < objectBase.Camera.VisiblePhysicsObjectCount; i++)
            {
                visiblePhysicsObject = objectBase.Camera.GetVisiblePhysicsObject(i);

                visiblePhysicsObject.GetBoundingBox(ref boundingBox);
                min = boundingBox.Min;
                max = boundingBox.Max;

                cameraVertices[0].Position.X = min.X;
                cameraVertices[0].Position.Y = min.Y;
                cameraVertices[0].Position.Z = min.Z;
                cameraVertices[0].Color = diffuseColor;

                cameraVertices[1].Position.X = max.X;
                cameraVertices[1].Position.Y = min.Y;
                cameraVertices[1].Position.Z = min.Z;
                cameraVertices[1].Color = diffuseColor;

                cameraVertices[2].Position.X = min.X;
                cameraVertices[2].Position.Y = min.Y;
                cameraVertices[2].Position.Z = min.Z;
                cameraVertices[2].Color = diffuseColor;

                cameraVertices[3].Position.X = min.X;
                cameraVertices[3].Position.Y = max.Y;
                cameraVertices[3].Position.Z = min.Z;
                cameraVertices[3].Color = diffuseColor;

                cameraVertices[4].Position.X = min.X;
                cameraVertices[4].Position.Y = min.Y;
                cameraVertices[4].Position.Z = min.Z;
                cameraVertices[4].Color = diffuseColor;

                cameraVertices[5].Position.X = min.X;
                cameraVertices[5].Position.Y = min.Y;
                cameraVertices[5].Position.Z = max.Z;
                cameraVertices[5].Color = diffuseColor;

                cameraVertices[6].Position.X = max.X;
                cameraVertices[6].Position.Y = max.Y;
                cameraVertices[6].Position.Z = max.Z;
                cameraVertices[6].Color = diffuseColor;

                cameraVertices[7].Position.X = min.X;
                cameraVertices[7].Position.Y = max.Y;
                cameraVertices[7].Position.Z = max.Z;
                cameraVertices[7].Color = diffuseColor;

                cameraVertices[8].Position.X = max.X;
                cameraVertices[8].Position.Y = max.Y;
                cameraVertices[8].Position.Z = max.Z;
                cameraVertices[8].Color = diffuseColor;

                cameraVertices[9].Position.X = max.X;
                cameraVertices[9].Position.Y = min.Y;
                cameraVertices[9].Position.Z = max.Z;
                cameraVertices[9].Color = diffuseColor;

                cameraVertices[10].Position.X = max.X;
                cameraVertices[10].Position.Y = max.Y;
                cameraVertices[10].Position.Z = max.Z;
                cameraVertices[10].Color = diffuseColor;

                cameraVertices[11].Position.X = max.X;
                cameraVertices[11].Position.Y = max.Y;
                cameraVertices[11].Position.Z = min.Z;
                cameraVertices[11].Color = diffuseColor;

                cameraVertices[12].Position.X = min.X;
                cameraVertices[12].Position.Y = max.Y;
                cameraVertices[12].Position.Z = min.Z;
                cameraVertices[12].Color = diffuseColor;

                cameraVertices[13].Position.X = min.X;
                cameraVertices[13].Position.Y = max.Y;
                cameraVertices[13].Position.Z = max.Z;
                cameraVertices[13].Color = diffuseColor;

                cameraVertices[14].Position.X = min.X;
                cameraVertices[14].Position.Y = max.Y;
                cameraVertices[14].Position.Z = min.Z;
                cameraVertices[14].Color = diffuseColor;

                cameraVertices[15].Position.X = max.X;
                cameraVertices[15].Position.Y = max.Y;
                cameraVertices[15].Position.Z = min.Z;
                cameraVertices[15].Color = diffuseColor;

                cameraVertices[16].Position.X = max.X;
                cameraVertices[16].Position.Y = min.Y;
                cameraVertices[16].Position.Z = max.Z;
                cameraVertices[16].Color = diffuseColor;

                cameraVertices[17].Position.X = max.X;
                cameraVertices[17].Position.Y = min.Y;
                cameraVertices[17].Position.Z = min.Z;
                cameraVertices[17].Color = diffuseColor;

                cameraVertices[18].Position.X = max.X;
                cameraVertices[18].Position.Y = min.Y;
                cameraVertices[18].Position.Z = max.Z;
                cameraVertices[18].Color = diffuseColor;

                cameraVertices[19].Position.X = min.X;
                cameraVertices[19].Position.Y = min.Y;
                cameraVertices[19].Position.Z = max.Z;
                cameraVertices[19].Color = diffuseColor;

                cameraVertices[20].Position.X = min.X;
                cameraVertices[20].Position.Y = max.Y;
                cameraVertices[20].Position.Z = max.Z;
                cameraVertices[20].Color = diffuseColor;

                cameraVertices[21].Position.X = min.X;
                cameraVertices[21].Position.Y = min.Y;
                cameraVertices[21].Position.Z = max.Z;
                cameraVertices[21].Color = diffuseColor;

                cameraVertices[22].Position.X = max.X;
                cameraVertices[22].Position.Y = max.Y;
                cameraVertices[22].Position.Z = min.Z;
                cameraVertices[22].Color = diffuseColor;

                cameraVertices[23].Position.X = max.X;
                cameraVertices[23].Position.Y = min.Y;
                cameraVertices[23].Position.Z = min.Z;
                cameraVertices[23].Color = diffuseColor;

                effect.View = view;
                effect.World = world;
                effect.Projection = projection;

                effect.VertexColorEnabled = true;
                effect.TextureEnabled = false;
                effect.LightingEnabled = false;

                effect.Techniques[0].Passes[0].Apply();

                demo.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, cameraVertices, 0, 12);
            }
        }

        public void DrawContactPoints(DrawMethodArgs args)
        {
            PhysicsScene scene = demo.Engine.Factory.PhysicsSceneManager.Get(args.OwnerSceneIndex);
            PhysicsObject objectBase = scene.Factory.PhysicsObjectManager.Get(args.OwnerIndex);

            if (!objectBase.Camera.Enabled) return;
            if (!objectBase.Camera.Active) return;
            if (!EnableDrawContactPoints) return;

            demo.GraphicsDevice.DepthStencilState = DepthStencilState.None;

            float time = args.Time;

            PhysicsObject visiblePhysicsObject;
            int contactPointCount;
            Vector3 start1, end1, start2, end2;

            objectBase.Camera.View.GetViewMatrix(ref view);
            objectBase.Camera.Projection.GetProjectionMatrix(ref projection);
            world = matrixIdentity;
            Color diffuseColor1 = Color.White;
            Color diffuseColor2 = Color.YellowGreen;

            for (int i = 0; i < objectBase.Camera.VisiblePhysicsObjectCount; i++)
            {
                visiblePhysicsObject = objectBase.Camera.GetVisiblePhysicsObject(i);

                for (int j = 0; j < visiblePhysicsObject.CollisionPairCount; j++)
                {
                    contactPointCount = visiblePhysicsObject.GetCollisionPairContactPointCount(j);

                    for (int k = 0; k < contactPointCount; k++)
                    {
                        visiblePhysicsObject.GetCollisionPairContactPointAnchor2(j, k, ref position);
                        visiblePhysicsObject.GetCollisionPairContactPointNormal(j, k, ref direction);

                        Vector3.Multiply(ref direction, 0.5f, out direction);

                        start1 = position;
                        end1 = direction;
                        Vector3.Add(ref start1, ref end1, out end1);

                        start2 = end1;
                        end2 = direction;
                        Vector3.Add(ref start2, ref end2, out end2);

                        effect.View = view;
                        effect.World = world;
                        effect.Projection = projection;

                        effect.VertexColorEnabled = true;
                        effect.TextureEnabled = false;
                        effect.LightingEnabled = false;

                        cameraVertices[0].Position = start1;
                        cameraVertices[0].Color = diffuseColor1;
                        cameraVertices[1].Position = end1;
                        cameraVertices[1].Color = diffuseColor1;

                        effect.Techniques[0].Passes[0].Apply();

                        demo.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, cameraVertices, 0, 1);

                        cameraVertices[0].Position = start2;
                        cameraVertices[0].Color = diffuseColor2;
                        cameraVertices[1].Position = end2;
                        cameraVertices[1].Color = diffuseColor2;

                        demo.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, cameraVertices, 0, 1);
                    }
                }
            }

            demo.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
        }
    }
}
